﻿
namespace Patterns4Net.Core
{
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Mono.Cecil;
    using Mono.Cecil.Rocks;
    using Attributes;

    /// <summary>
    /// Provides functionality to get patterns meta-data using <see cref="Provider"/>.
    /// </summary>
    [Export]
    public class PatternsLocator
    {
        private readonly IEnumerable<IPatternsProvider> providers;

        [ImportingConstructor]
        public PatternsLocator([ImportMany]IPatternsProvider[] patternsProviders)
        {
            Contract.Requires(patternsProviders != null);
            Contract.Requires(Contract.ForAll(patternsProviders, x => x != null));
            this.providers = patternsProviders;
        }

        public IEnumerable<IPattern> GetPatternsOf<TMember>(TMember member) where TMember : IMemberDefinition
        {
            Contract.Requires(member != null);
            Contract.Ensures(Contract.Result<IEnumerable<IPattern>>() != null);

            // We use ISet to avoid multiple entries.
            var result = new HashSet<IPattern>();
            foreach (var patternsProvider in this.providers)
            {
                Contract.Assert(result != null);
                Contract.Assume(patternsProvider != null);
                result.UnionWith(patternsProvider.GetPatterns(member));
            }

            return result;
        }

        public IEnumerable<MethodAndPatterns> GetMethodAndPatternsOf(TypeDefinition type)
        {
            var result = from method in type.Methods
                         select new MethodAndPatterns(method, GetPatternsOf(method));
            return result.Where(x => x.Patterns.Count() != 0);
        }

        public PatternsLocationResult GetPatternsFromAssembly(AssemblyDefinition assembly)
        {
            Contract.Requires(assembly != null);
            Contract.Requires(assembly.Modules != null);
            Contract.Ensures(Contract.Result<PatternsLocationResult>() != null);

            var allTypes = assembly.Modules.SelectMany(x => x.GetAllTypes());
            var classes = from type in allTypes
                          select new TypeAndPatterns(type, this.GetPatternsOf(type));
            classes = classes.ToArray();
            classes = classes.Where(x => x.Patterns.Count() != 0);

            var methods = allTypes.SelectMany(this.GetMethodAndPatternsOf);

            return new PatternsLocationResult(classes, methods);
        }

        [ContractInvariantMethod]
        private void ProvidersEnumerationIsNotNull() 
        {
            Contract.Invariant(this.providers != null);
        }
    }
}
